/*
 * DownloadsListModel.java
 *
 * Copyright 2007, 2008 Tiberiumlabs
 *
 * This file is part of Tiberiumlabs Lailaps.
 *
 * Tiberiumlabs Lailaps is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tiberiumlabs Lailaps is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.tiberiumlabs.lailaps.list;

import java.awt.Adjustable;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;
import javax.swing.table.AbstractTableModel;
import org.tiberiumlabs.lailaps.resources.ImageLoader;
import org.tiberiumlabs.lailaps.settings.ApplicationSettings;
import org.tiberiumlabs.lailaps.settings.Settings;
import org.tiberiumlabs.util.NumberFormat;

/**
 *
 * @author <a href="mailto:paranoid.tiberiumlabs@gmail.com">Paranoid</a>
 */
public abstract class DownloadsListModel extends AbstractTableModel implements StatusConstants, AdjustmentListener {

    private ApplicationSettings applicationSettings = Settings.getApplicationSettings();
    private NumberFormat numberFormat = NumberFormat.sharedInstance();

    // List with downloads data
    private List<DownloadItem> dataList;
    private HashMap<String, Integer> dataMap = new HashMap<String, Integer>();
    private LinkedList<HistoryItem> historyList = new LinkedList<HistoryItem>();

    public DownloadsListModel() {
    }

    // <editor-fold defaultstate="collapsed" desc=" TableModel implementation ">
    @Override
    public int getRowCount() {
        return dataList.size();
    }

    @Override
    public int getColumnCount() {
        return 11;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        DownloadItem item = dataList.get(rowIndex);
        switch (columnIndex) {
            case 0:
                return getStatus(item);
            case 1:
                return item.getUrl();
            case 2:
                return item.getOutputFile();
            case 3:
                return item.getSafeOutputPath();
            case 4:
                return numberFormat.formatSize(item.getCompletedSize());
            case 5:
                return numberFormat.formatSpeed(item.getSpeed());
            case 6:
                return numberFormat.formatSize(item.getFullSize());
            case 7:
                return getTimeElapsed(item);
            case 8:
                return getTimeEstimated(item);
            default:
                return item;
        }
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    }

    // </editor-fold>

    // methods for this class:
    private Object getStatus(DownloadItem item) {
        switch (item.getStatus()) {
            case STOPPED:
                return ImageLoader.getStatusStoppedIcon();
            case PREPARING:
                return ImageLoader.getStatusPreparingIcon();
            case WORKING:
                return ImageLoader.getStatusWorkingIcon();
            case WAITING:
                return ImageLoader.getStatusWaitingIcon();
            case PAUSED:
                return ImageLoader.getStatusPausedIcon();
            case COMPLETED:
                return ImageLoader.getStatusCompletedIcon();
            case ERROR:
                return ImageLoader.getStatusErrorIcon();
            default:
                return ImageLoader.getStatusStoppedIcon();
        }
    }

    // <editor-fold defaultstate="collapsed" desc=" methods to work with downloads list ">

    public DownloadItem getItem(int index) {
        return dataList.get(index);
    }

    public int addItem(DownloadItem item) {
        if (containsItem(item)) {
            return -1;
        }
        dataMap.put(item.getUrl(), null);
        if (applicationSettings.isAddToFront()) {
            dataList.add(0, item);
            fireTableRowsInserted(0, 0);
            return 0;
        }
        dataList.add(item);
        int i = dataList.size() - 1;
        fireTableRowsInserted(i, i);
        return i;
    }

    public int insertItem(int position, DownloadItem item) {
        if (containsItem(item)) {
            return -1;
        }
        if (position < 0 || position >= dataList.size()) {
            // addItem method puts into map itself
            return addItem(item);
        }
        dataMap.put(item.getUrl(), null);
        dataList.add(position, item);
        fireTableRowsInserted(position, position);
        return position;
    }

    public int indexOfItem(DownloadItem item) {
        if (item == null) {
            return -1;
        }
        return dataList.indexOf(item);
    }

    public int removeItem(DownloadItem item) {
        int i = dataList.indexOf(item);
        if (i >= 0) {
            dataList.remove(i);
            dataMap.remove(item.getUrl());
            fireTableRowsDeleted(i, i);
            addHistory(item);
        }
        return i;
    }

    public int removeItem(int index) {
        DownloadItem item = dataList.remove(index);
        dataMap.remove(item.getUrl());
        fireTableRowsDeleted(index, index);
        addHistory(item);
        return index;
    }

    public void removeItemsList(int[] rows) {
        for (int i = rows.length - 1; i >= 0; i--) {
            DownloadItem item = dataList.remove(rows[i]);
            dataMap.remove(item.getUrl());
            addHistory(item);
        }
        fireTableDataChanged();
    }

    public boolean containsItem(DownloadItem item) {
        return dataMap.containsKey(item.getUrl());
    }

    public void addHistory(DownloadItem item) {
        HistoryItem h = item.toHistory();
        synchronized (historyList) {
            historyList.addLast(h);
        }
        new ListStorage().storeHistory();
    }

    protected List<DownloadItem> getDataList() {
        return dataList;
    }

    protected void setDataList(List<DownloadItem> inputList) {
        HashMap<String, Integer> map = rebuildMap(inputList, new HashMap<String, Integer>(inputList.size()));
        this.dataList = inputList;
        this.dataMap = map;
        fireTableDataChanged();
    }

    private HashMap<String, Integer> rebuildMap(List<DownloadItem> list, HashMap<String, Integer> map) {
        if (list instanceof RandomAccess) {
            for (int i = 0; i < list.size(); i++) {
                map.put(list.get(i).getUrl(), null);
            }
        }
        else {
            Iterator<DownloadItem> iterator = list.iterator();
            while (iterator.hasNext()) {
                map.put(iterator.next().getUrl(), null);
            }
        }
        return map;
    }

    protected LinkedList<HistoryItem> getHistoryList() {
        return historyList;
    }

    protected void fillHistoryMap(HashMap<String, Integer> map) {
        dataMap.putAll(map);
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" move rows methods ">

    public void moveItems(int newIndex, int[] indicies) {

        // do nothing if dropping on same row:
        if (indicies.length > 0 && newIndex == indicies[0]) {
            return;
        }

        // do nothing if dropping outside of safa range
        if (newIndex < 0) {
            return;
        }

        DownloadItem dest = getItem(newIndex);

        // Copy the objects to a temporary ArrayList
        ArrayList<DownloadItem> objects = new ArrayList<DownloadItem>();
        for (int i = 0; i < indicies.length; i++) {
            objects.add(dataList.get(indicies[i]));
        }

        // Delete the objects from the list
        for (int i = indicies.length - 1; i >= 0; i--) {
            dataList.remove(indicies[i]);
        }

        newIndex = indexOfItem(dest);

        // Insert the items at the new location
        // Handle the case where the items are being added to the end of the list
        if (newIndex >= dataList.size() || newIndex == -1) {
            // Add the items
            dataList.addAll(objects);
        } else {
            // Insert the items
            //dataList.addAll(newIndex++, objects);
            dataList.addAll(newIndex, objects); // no need to increment after invoking. may be better to do ++newIndex?
        }

        // Tell the model to update view
        fireTableDataChanged();
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" other item params getters ">
    private String getTimeElapsed(DownloadItem item) {
        return "";
    }
    private String getTimeEstimated(DownloadItem item) {
        return "";
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" adjustment listener ">
    private boolean inTheEnd = false;
    private int prevMax = 0;
    public void adjustmentValueChanged(AdjustmentEvent e) {
        // TODO do something to be more intuitive:
        // if current position is not maximum or minimum - stay there.
        // if maximum or minimum - move.

        Adjustable adjustable = e.getAdjustable();
        int min = adjustable.getMinimum();
        int max = adjustable.getMaximum();
        int value = adjustable.getValue();
        int visible = adjustable.getVisibleAmount();
        int total = max - min;

        if (visible == total) {
            return;
        }

        if (prevMax < max && !applicationSettings.isAddToFront() && inTheEnd) {
            // need previous values to calculate if it was in the end of list.
            adjustable.setValue(max);
            prevMax = max;
            return;
        }

        if ((value + visible) == max) {
            inTheEnd = true;
        }
        else {
            inTheEnd = false;
        }
        prevMax = max;
    }
    // </editor-fold>

}
